﻿using System;
using System.Runtime.InteropServices;
using SkiaSharp;

namespace PKHeX.Drawing
{
    /// <summary>
    /// Image Layering/Blending Utility
    /// </summary>
    public static class ImageUtil
    {
        public static SKBitmap LayerImage(SKBitmap baseLayer, SKBitmap overLayer, int x, int y, double transparency)
        {
            if (baseLayer == null)
                return overLayer;
            overLayer = ChangeOpacity(overLayer, transparency);
            return LayerImage(baseLayer, overLayer, x, y);
        }

        public static SKBitmap LayerImage(SKBitmap baseLayer, SKBitmap overLayer, int x, int y)
        {
            if (baseLayer == null)
                return overLayer;
            var img = baseLayer.Copy();
            var rect = new SKRect(x, y, overLayer.Width + x, overLayer.Height + y);
            using var gr = new SKCanvas(img);
            gr.DrawBitmap(overLayer, rect);
            return img;
        }

        public static SKBitmap ChangeOpacity(SKBitmap img, double trans)
        {
            if (img == null)
                return null;

            var bmp = img.Copy();
            var data = GetBitmapData(img);
            SetAllTransparencyTo(data, trans);
            SetBitmapData(bmp, data);
            return bmp;
        }

        public static SKBitmap ChangeAllColorTo(SKBitmap img, SKColor c)
        {
            if (img == null)
                return null;

            var bmp = img.Copy();
            var data = GetBitmapData(img);
            ChangeAllColorTo(data, c);
            SetBitmapData(bmp, data);
            return bmp;
        }

        public static SKBitmap ToGrayscale(SKBitmap img)
        {
            if (img == null)
                return null;

            var bmp = img.Copy();
            var data = GetBitmapData(img);
            SetAllColorToGrayScale(data);
            SetBitmapData(bmp, data);
            return bmp;
        }

        public static byte[] GetBitmapData(SKBitmap bmp)
        {
            var ptr = bmp.GetPixels();
            var data = new byte[bmp.Width * bmp.Height * 4];
            Marshal.Copy(ptr, data, 0, data.Length);
            return data;
        }

        public static void SetBitmapData(SKBitmap bmp, byte[] data)
        {
            var ptr = bmp.GetPixels();
            Marshal.Copy(data, 0, ptr, data.Length);
        }

        public static void SetAllUsedPixelsOpaque(byte[] data)
        {
            for (int i = 0; i < data.Length; i += 4)
            {
                if (data[i + 3] != 0)
                    data[i + 3] = 0xFF;
            }
        }

        public static void RemovePixels(byte[] pixels, byte[] original)
        {
            for (int i = 0; i < original.Length; i += 4)
            {
                if (original[i + 3] == 0)
                    continue;
                pixels[i + 0] = 0;
                pixels[i + 1] = 0;
                pixels[i + 2] = 0;
                pixels[i + 3] = 0;
            }
        }

        private static void SetAllTransparencyTo(byte[] data, double trans)
        {
            for (int i = 0; i < data.Length; i += 4)
                data[i + 3] = (byte)(data[i + 3] * trans);
        }

        public static void ChangeAllColorTo(byte[] data, SKColor c)
        {
            byte R = c.Red;
            byte G = c.Green;
            byte B = c.Blue;
            for (int i = 0; i < data.Length; i += 4)
            {
                if (data[i + 3] == 0)
                    continue;
                data[i + 0] = B;
                data[i + 1] = G;
                data[i + 2] = R;
            }
        }

        private static void SetAllColorToGrayScale(byte[] data)
        {
            for (int i = 0; i < data.Length; i += 4)
            {
                if (data[i + 3] == 0)
                    continue;
                byte greyS = (byte)(((0.3 * data[i + 2]) + (0.59 * data[i + 1]) + (0.11 * data[i + 0])) / 3);
                data[i + 0] = greyS;
                data[i + 1] = greyS;
                data[i + 2] = greyS;
            }
        }

        public static void GlowEdges(byte[] data, byte blue, byte green, byte red, int width, int reach = 3, double amount = 0.0777)
        {
            PollutePixels(data, width, reach, amount);
            CleanPollutedPixels(data, blue, green, red);
        }

        private static void PollutePixels(byte[] data, int width, int reach, double amount)
        {
            int stride = width * 4;
            int height = data.Length / stride;
            for (int i = 0; i < data.Length; i += 4)
            {
                // only pollute outwards if the current pixel isn't transparent
                if (data[i + 3] == 0)
                    continue;

                int x = (i % stride) / 4;
                int y = (i / stride);
                Pollute(x, y);
            }

            void Pollute(int x, int y)
            {
                int left = Math.Max(0, x - reach);
                int right = Math.Min(width - 1, x + reach);
                int top = Math.Max(0, y - reach);
                int bottom = Math.Min(height - 1, y + reach);
                for (int i = left; i <= right; i++)
                {
                    for (int j = top; j <= bottom; j++)
                    {
                        // update one of the color bits
                        // it is expected that a transparent pixel RGBA value is 0.
                        var c = 4 * (i + (j * width));
                        data[c + 0] += (byte)(amount * (0xFF - data[c + 0]));
                    }
                }
            }
        }

        private static void CleanPollutedPixels(byte[] data, byte blue, byte green, byte red)
        {
            for (int i = 0; i < data.Length; i += 4)
            {
                // only clean if the current pixel isn't transparent
                if (data[i + 3] != 0)
                    continue;

                // grab the transparency from the donor byte
                var transparency = data[i + 0];
                if (transparency == 0)
                    continue;

                data[i + 0] = blue;
                data[i + 1] = green;
                data[i + 2] = red;
                data[i + 3] = transparency;
            }
        }

        public static SKColor ColorBaseStat(int v)
        {
            const float maxval = 180; // shift the green cap down
            float x = 100f * v / maxval;
            if (x > 100)
                x = 100;
            double red = 255f * (x > 50 ? 1 - (2 * (x - 50) / 100.0) : 1.0);
            double green = 255f * (x > 50 ? 1.0 : 2 * x / 100.0);

            return Blend(new SKColor((byte)red, (byte)green, 0), new SKColor(0, 0, 0), 0.4);
        }

        public static SKColor Blend(SKColor color, SKColor backColor, double amount)
        {
            byte r = (byte)((color.Red * amount) + (backColor.Red * (1 - amount)));
            byte g = (byte)((color.Green * amount) + (backColor.Green * (1 - amount)));
            byte b = (byte)((color.Blue * amount) + (backColor.Blue * (1 - amount)));
            return new SKColor(r, g, b);
        }
    }
}
